#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>

#include "peekpoke.h"
#include "ep93xx_adc.h"

#define DATA_PAGE    0x12C00000
#define CALIB_LOC    2027          //location of calibration values
#define NUM_SAMPLES  5
#define NUM_CHANNELS 4

/* globals */
static unsigned long adc_page, syscon_page;
char *dr_page;

/*Calculate the adc value corresponding to 0V*/
//val1 is the ADC val coresponding to 0.833V
//val2 is the ADC val corresponging to 2.5V
int calcZeroVal(int val1, int val2)
{
	val2 += 0x10000;
	return (int)(val1-(((val2-val1)/(2.5-0.833))*0.833));
}


//return value of 1 indicates the board has no calibration values
//return value of 0 indicates the board has calibration values
int read_calibration(int buf[NUM_CHANNELS][2])
{
	int i,j,k = 0;
	unsigned short cal[NUM_CHANNELS*2];
	// read 16 calibration bytes into buffer
	FILE *f = fopen("/etc/ADC-calibration.dat","r");

	if (!f) goto empty_calibration;

	printf("Non-virgin board detected, evaluating stored "
		"calibration values\n");
	printf("Stored Calibration values [");

	if (fread(cal,NUM_CHANNELS*4,1,f) == 1)
	{
		for(j=0;j<2;j++)
			for(i=0;i<NUM_CHANNELS;i++)
			{
				printf("0x%x", cal[k]);
				buf[i][j] = cal[k];
				k++;

				if(k < NUM_CHANNELS*2)
					printf(", ");

			}
			printf("]\n");
		return 1;
	}
	empty_calibration:

	printf("/etc/ADC-calibration.dat not found or it's not readable\n");

	return 0;
}

void write_calibration(int cal[NUM_CHANNELS][2])
{
	unsigned short buf[16];
	int i,j,k=0;
	FILE *f = fopen("/etc/ADC-calibration.dat","w");

	//Convert 32 bit vals to 16 bit vals
	for(j=0;j<2;j++)
		for(i=0;i<NUM_CHANNELS;i++)
		{
			buf[k] = (unsigned short)cal[i][j];
			k++;

		}
	if (!f) goto unwrite_calibration;

	if (fwrite(buf,NUM_CHANNELS*4,1,f) == 1) return;

	unwrite_calibration:

	printf("Problem writing /etc/ADC-calibration.dat: %m\n");
}

static void erase_calibration()
{
	printf("Erasing calibration values...\n");
	unlink("/etc/ADC-calibration.dat");
}

int check_calibration(int cal[NUM_CHANNELS][2],
	int stored_cal[NUM_CHANNELS][2], int state)
{
	double pcnt_diff;
	int i,j,erase_cal =0;
	int failure = 0;

	if(state == 0)	//no calibration values
	{
		printf("Virgin board detected...\n");
		for(j=0;j<2;j++)
		{

			for(i=0;i<NUM_CHANNELS;i++)
			{

				if(j == 0)
					pcnt_diff = (((double)abs(0xa000 -
				  	cal[i][j])) / 0xa000) * 100;
				else
					pcnt_diff = (((double)abs(0x3300 -
				  	cal[i][j])) / 0x3300) * 100;

				if(pcnt_diff > 10)
				{
					printf("Calculated calibration "
					"values out of range...\n");

					exit(-1);
				}

			}
		}

		write_calibration(cal);

	} else		//calibration values read
	{

		for(j=0;j<2;j++)
		{
			for(i=0;i<NUM_CHANNELS;i++)
			{

				pcnt_diff = (((double)abs(stored_cal[i][j] -
				  cal[i][j])) / stored_cal[i][j])
				  * 100;
				if(pcnt_diff > 0.25)
				{
					if(!failure)
					{
						printf("Calibration values out"
							"of range\n");
						failure = 1;
						erase_cal = 1;
					}
					printf("\tChannel %d: %3.3f%%\n",i
					  , pcnt_diff);
				}
			}
		}
	}

	if(erase_cal) erase_calibration();
	if(failure) return 0;

	return 1;
}

void setDR(char *x,int n,int val)
{
	if (n < 0 || n > 8)
		return;

	x[0] = (x[0] & ~(1 << n)) | (val ? (1<<n) : 0);
}

void setD(char *x,int n,int val)
{
	if (n < 0 || n > 8)
		return;

	x[2] = (x[2] & ~(1 << n)) | (val ? (1<<n) : 0);
}

double get_volts(int val, int zero, int range)
{
	if(val <= 0x7000)
		val = val + 0x10000;

	val = val - zero;

	return  ((double)val * 3.3) / range;
}

void calc_calibration(int calibration[NUM_CHANNELS][2],
	int adc_result_1[NUM_CHANNELS][NUM_SAMPLES],
	int adc_result_2[NUM_CHANNELS][NUM_SAMPLES])
{
	int i, j;

	/* zero out our calibration values */
	for(i = 0; i < NUM_CHANNELS; i++)
		for(j = 0; j < 2; j++)
			calibration[i][j] = 0;

	//convert 0.833V vals to 0V vals
	for(i=0;i<NUM_CHANNELS;i++)
	{
		for(j=0;j<NUM_SAMPLES;j++)
		{
			if(i % 2)//odd channels
				adc_result_1[i][j] =
				  calcZeroVal(adc_result_1[i][j],
				  adc_result_1[0][j]);
			else
				adc_result_2[i][j] =
				  calcZeroVal(adc_result_2[i][j],
				  adc_result_2[1][j]);
		}
	}

	//sum the readings
	for(i = 0; i < NUM_CHANNELS; i++)
	{
		for(j = 0; j < NUM_SAMPLES; j++ )
		{
			if(i % 2 == 0 )
			{
				//0.833 volt values
				calibration[i][0] = adc_result_2[i][j]
				  + calibration[i][0];
				//2.5 volt values
				calibration[i][1] = adc_result_1[i][j]
				  + calibration[i][1];
			} else
			{
				//0.833 volt values
				calibration[i][0] = adc_result_1[i][j]
				  + calibration[i][0];
				//2.5 volt values
				calibration[i][1] = adc_result_2[i][j]
				  + calibration[i][1];
			}
		}
	}

	printf("Calculated Calibration values [");
	for(j = 0; j < 2; j++)
	{
		for(i = 0; i < NUM_CHANNELS; i++)
		{
			calibration[i][j] = (calibration[i][j] / NUM_SAMPLES);

			printf("0x%x", calibration[i][j]);

			if((i == NUM_CHANNELS-1) && (j == 1))
				printf("]\n");
			else
				printf(", ");
		}
	}
}

/************************************************************************
 *DESCRIPTION: Read the EP93xx onboard ADC. Discard the first
 *two samples then save the next NUM_SAMPLES.
 ***********************************************************************/
static void read_7xxx_adc(int adc_result[NUM_CHANNELS][NUM_SAMPLES])
{
	int i, j, cur_ch;

	for(i = 0; i < NUM_CHANNELS; i++)
	{
		switch(i)
		{
			case 0:
				cur_ch = ADC_CH0;
			break;
			case 1:
				cur_ch = ADC_CH1;
			break;
			case 2:
				cur_ch = ADC_CH2;
			break;
			case 3:
				cur_ch = ADC_CH3;
			break;
			case 4:
				cur_ch = ADC_CH4;
			break;

		}

		//discard first two samples
		read_channel(adc_page, cur_ch);
		read_channel(adc_page, cur_ch);

		//read more samples
		for(j = 0; j < NUM_SAMPLES; j++)
		{
			usleep(10000);
			adc_result[i][j] = read_channel(adc_page, cur_ch);
		}
	}
}

int test_ADC(int calibration[NUM_CHANNELS][2])
{
	int adc_result_1[NUM_CHANNELS][NUM_SAMPLES];
	int adc_result_2[NUM_CHANNELS][NUM_SAMPLES];
	int i, j, return_val;
	int failure = 0;
	double voltage;

	setD(dr_page, 0, 1);	//ADC1 = ADC3 = 0.833V
	setD(dr_page, 2, 1);	//ADC0 = ADC2 = 2.5V
	read_7xxx_adc(adc_result_1);

	setD(dr_page, 0, 0);	//ADC1 = ADC3 = 2.5V
	setD(dr_page, 2, 1);	//ADC0 = ADC2 = 0.833V
	read_7xxx_adc(adc_result_2);

	//verify results are within range
	for(i=0;i<NUM_CHANNELS;i++)
	{
		for(j=0;j<NUM_SAMPLES;j++)
		{
			//use the datasheet values
			voltage = get_volts(adc_result_1[i][j], 0x9E58, 0xC350);

			//even channels 2.5V(+-150mV)
			if(i % 2 == 0 &&
			  (voltage < 2.35 || voltage > 2.65))
			{
				if(!failure)
				{
					failure = 1;
					printf("EP93XX ADC out of range\n");
				}
				printf("\tChannel %d: %3.3fV"
				  "(expected 2.5V +- 150mV)\n", i, voltage);
			//odd channels 0.833(+-50mV)
			}
			else if(i % 2 == 1 &&
			    (voltage < 0.333 || voltage > 1.333))
			{
				if(!failure)
				{
					failure = 1;
					printf( "EP93xx ADC out of range\n");
				}

				printf("\tChannel %d: %3.3fV"
				  "(expected 0.833V +- 50mV)\n", i, voltage);
			}


			//use the datasheet values
			voltage = get_volts(adc_result_2[i][j], 0x9E58, 0xC350);

			//odd channels 2.5V(+-150mV)
			if(i % 2 == 1 &&
			  (voltage < 2.35 || voltage > 2.65))
			{
				if(!failure)
				{
					failure = 1;
					printf("EP93XX ADC out of range\n");
				}
				printf("\tChannel %d: %3.3fV"
				  "(expected 2.5V +- 150mV)\n", i, voltage);
			//even channels 0.833(+-50mV)
			}
			else if(i % 2 == 0 &&
			    (voltage < 0.333 || voltage > 1.333))
			{
				if(!failure)
				{
					failure = 1;
					printf( "EP93xx ADC out of range\n");
				}

				printf("\tChannel %d: %3.3fV"
				  "(expected 0.833V +- 50mV)\n", i, voltage);
			}
		}
	}

	calc_calibration(calibration, adc_result_1, adc_result_2);

	if(failure)
		return_val = 0;
	else return_val = 1;

	return return_val;
}


int main(void)
{
	int calibration[NUM_CHANNELS][2];
	int stored_calibration[NUM_CHANNELS][2];
	int ret_val, state;
	int devmem = open("/dev/mem", O_RDWR|O_SYNC);
	assert(devmem != -1);

	dr_page = mmap(0, getpagesize(), PROT_READ|PROT_WRITE,
	  MAP_SHARED, devmem, DATA_PAGE);
	assert(&dr_page != MAP_FAILED);
	adc_page = (unsigned long)mmap(0, getpagesize(), PROT_READ|PROT_WRITE,
		MAP_SHARED, devmem, ADC_PAGE);
	assert(&adc_page != MAP_FAILED);
	syscon_page = (unsigned long)mmap(0, getpagesize(), PROT_READ|PROT_WRITE
	  , MAP_SHARED, devmem, SYSCON_PAGE);
        assert(&syscon_page != MAP_FAILED);

	init_ADC(adc_page, syscon_page);
	setDR(dr_page, 0, 1);
	setDR(dr_page, 1, 0);
	setDR(dr_page, 2, 1);
	setDR(dr_page, 3, 0);

	if(test_ADC(calibration))
	{
		printf("ADC tested ok(data sheet values)\n");
		state = read_calibration(stored_calibration);

		if(check_calibration(calibration, stored_calibration, state))
			ret_val = 0;
		else
			ret_val = 1;

	}
	else
		ret_val = 1;

	close(devmem);

	return ret_val;
}
